﻿using System;
using System.Collections.Generic;
using System.Text;
using Barbar.MapReport.Common;
using Barbar.MapReport.Common.Drawing;
using System.Drawing;

namespace Barbar.MapReport.HtmlRender.Operations.Legends {
  internal class LegendFactory {
    private List<IBusinessValue> m_Items;
    private decimal m_Total;
    private int m_CategoriesCount;
    private ILegendDescription m_Description;
    private readonly Color EmptyColor = Color.White;

    public LegendFactory(IEnumerable<IBusinessValue> items, int categoriesCount, ILegendDescription description) {
      if (items == null)
        throw new ArgumentNullException("items");

      if (description == null)
        throw new ArgumentNullException("description");

      if (categoriesCount < 1)
        throw new ArgumentOutOfRangeException("categoriesCount", categoriesCount, "Categories count must be greater or equal to 1.");

      m_Items = new List<IBusinessValue>(items);
      m_Items.Sort(BusinessValueHelper.Comparer);
      foreach (IBusinessValue value in m_Items)
        m_Total += value.Value;

      m_CategoriesCount = categoriesCount;
      m_Description = description;
    }

    private void MergeTwoRanges(LegendRangeList legend, LinkedListNode<LegendRange> first, LinkedListNode<LegendRange> second) {
      if (first != second) {
        first.Value.From = Math.Min(first.Value.From, second.Value.From);
        first.Value.To = Math.Max(first.Value.To, second.Value.To);
        legend.Remove(second);
      }
    }

    private void MergeAllRanges(LegendRangeList legend) {
      bool merge;
      do {
        merge = false;
        LinkedListNode<LegendRange> node = legend.First;
        while (node != null) {
          LinkedListNode<LegendRange> nextNode = node.Next;
          if (nextNode != null) {
            if (node.Value.To == nextNode.Value.To) {
              MergeTwoRanges(legend, node, nextNode);
              merge = true;
              break;
            }
          }
          node = nextNode;
        }
      } while (merge);
    }

    private void SmoothRanges(LegendRangeList legend) {
      LinkedListNode<LegendRange> node = legend.First;
      while (node != null && node.Next != null) {
        decimal delta = node.Value.From - node.Next.Value.To;
        decimal value = node.Next.Value.To + delta * node.Next.Value.To / node.Value.From;
        node.Value.From = value;
        node.Next.Value.To = value;
        node = node.Next;
      }
    }

    public LegendRangeList CreateLegend() {
      LegendRangeList legend = new LegendRangeList();
      decimal categoryRange = m_Total / m_CategoriesCount;

      Stack<IBusinessValue> stack = new Stack<IBusinessValue>(m_Items.Count);
      foreach (IBusinessValue businessValue in m_Items)
        stack.Push(businessValue);

      while (stack.Count > 0) {
        LegendRange entry = new LegendRange();
        legend.AddLast(entry);
        entry.To = stack.Peek().Value;
        while ((entry.Total <= 0.95m * categoryRange || legend.Count == m_CategoriesCount) && stack.Count > 0) {
          decimal value = stack.Pop().Value;
          entry.Total += value;
          entry.From = value;
        }
      }
      MergeAllRanges(legend);
      SmoothRanges(legend);
      LegendRangeList result = RevertRanges(legend);
      AssignColorToRanges(result);
      return result;
    }

    private void AssignColorToRanges(LegendRangeList legend) {
      LinkedListNode<LegendRange> node = legend.First;
      decimal i = 1;
      while (node != null) {
        if (node.Value.From == 0.0m)
          node.Value.Color = Color.White;
        else {
          decimal colorMultiplier = i / (decimal)legend.Count;
          node.Value.Color = Color.FromArgb(
            (int)(EmptyColor.R + colorMultiplier * (m_Description.Color.R - EmptyColor.R)),
            (int)(EmptyColor.G + colorMultiplier * (m_Description.Color.G - EmptyColor.G)),
            (int)(EmptyColor.B + colorMultiplier * (m_Description.Color.B - EmptyColor.B))
          );
        }
        node = node.Next;
        i++;
      }
    }

    private LegendRangeList RevertRanges(LegendRangeList legend) {
      LegendRangeList revertedLegend = new LegendRangeList();
      LinkedListNode<LegendRange> start = legend.First;
      while (start != null) {
        revertedLegend.AddFirst(start.Value);
        start = start.Next;
      }
      return revertedLegend;
    }
  }
}